package com.ucode.springboot.starter.security.handler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ucode.tool.base.ResponseResult;
import com.ucode.tool.base.ResultCodeEnum;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

/**
 * Spring security其他异常处理类
 * 如果不配置此类，则Spring security默认会跳转到登录页面
 * 注意:如果全局统一异常处理中捕捉了Exception，则这里不会起效，这时需要在全局异常里单独处理本类中处理的异常
 * @author: liliang
 * @date: 2019年12月25日 上午10:56:23
 */
@Component
public class RestAuthenticationExceptionHandler implements AuthenticationEntryPoint {
    
    private static Log log = LogFactory.get(RestAuthenticationExceptionHandler.class);
    @Autowired
    private ObjectMapper objectMapper;
    
    @SuppressWarnings("rawtypes")
    public static Map<Class, ResponseResult<Object>> responseResultCache = new HashMap<Class, ResponseResult<Object>>();
    static {
        responseResultCache.put(InsufficientAuthenticationException.class, ResponseResult.result(ResultCodeEnum.UNAUTHENTICATED));
        responseResultCache.put(BadCredentialsException.class, ResponseResult.result(ResultCodeEnum.LOGIN_ERROR));
        responseResultCache.put(AccountExpiredException.class, ResponseResult.result(ResultCodeEnum.ACCOUNT_EXPIRED));
        responseResultCache.put(LockedException.class, ResponseResult.result(ResultCodeEnum.ACCOUNT_LOCKED));
        responseResultCache.put(CredentialsExpiredException.class, ResponseResult.result(ResultCodeEnum.PASSWORD_EXPIRED));
    }
    
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        if(log.isDebugEnabled())
            log.error(authException,"Spring security其他异常处理");
        
        /**
         * 1、校验权限阶段:如果请求未带token或token过期、错误或未找到用户账户或用户账户状态异常均会抛出
         * InsufficientAuthenticationException->Full authentication is required to access this resource需要认证后才能访问资源
         * 2、用户登录认证阶段:
         *   a、如果用户名或者密码错误均会抛出BadCredentialsException异常
         *   b、用户账户无效(即UserDetails->isAccountNonExpired=false)时会抛出AccountExpiredException异常
         *   c、用户账户被锁定(即UserDetails->isAccountNonLocked=false)时会抛出LockedException异常
         *   d、用户账户密码无效(即UserDetails->isCredentialsNonExpired=false)时：
         *      1)、如果用户输入的密码是错的则抛出BadCredentialsException异常
         *      2)、如果用户输入的密码是对的则抛出CredentialsExpiredException异常->凭证过期异常
         *   
         */
        
        ResponseResult<Object> result = responseResultCache.get(authException.getClass());
        if(result == null)
            result = ResponseResult.result(ResultCodeEnum.SERVER_ERROR);
        
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.setStatus(200);
        response.getWriter().println(objectMapper.writeValueAsString(result));
        response.getWriter().flush();
    }
}
